// Copyright 2021 Tencent. All rights reserved.
// Author: billbai <billbai@tencent.com>

#ifndef OCSENGINE_RFIX_RFIX_H_
#define OCSENGINE_RFIX_RFIX_H_

// RFIX Wrapper for OCSEngine

#import <Foundation/Foundation.h>
#import <RFIX/RFIXBuildConfig.h>
#import <RFIX/RFIXAnnotations.h>

#if defined(__cplusplus)
#define RFIX_EXTERN extern "C"
#else
#define RFIX_EXTERN extern
#endif

#define RFIX_EXPORT RFIX_EXTERN

#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#define RFIXImage UIImage
#elif TARGET_OS_OSX
#import <AppKit/AppKit.h>
#define RFIXImage NSImage
#endif

#if RFIX_BUILD_CONFIG_IS_NONRESTRICTED
#import <RFIX/RFIXForNonRestricted.h>
#endif  // RFIX_BUILD_CONFIG_IS_NONRESTRICTED

NS_ASSUME_NONNULL_BEGIN

//
// Public APIs For RFIX SDK User
// 以下接口供集成 RFIX SDK 方调用
//

/// 资源下载成功（非主线程通知），在通知的 userInfo 中将传递以下资源信息
/// userInfo: {
///     isDisabled = 0;             // 是否因为安全模式禁用
///     localRelativePath = "";     // 沙盒中的相对路径
///     md5 = "";                   // 文件哈希
///     needLoad = 0;               // 是否需要加载（下载时为0）
///     versionID = @"1033";        // 配置任务ID【和Bugly上的标签一致，本地补丁为 0】
///     isCritical = NO             // 是否关键补丁，使用者可根据此 flag 做自定义逻辑
///     isImmediatelyEffective = NO // RFIX SDK 首次下载到补丁后，自动立即加载该补丁，
///                                 // 无需等到下次重启。
///                                 // NOTE: 注意⚠️，如果启动时已经加载了补丁，则当次不会
///                                 // 自动加载，而是下次重启才加载（不能违反只能加载一次原则）
/// }
RFIX_EXPORT NSNotificationName const RFIXResourceDownloadSuccessNotification;

/// 资源加载成功（非主线程通知，在通知的 userInfo 中将传递以下资源信息（只有补丁走这个通知，插件看加载返回值）
/// userInfo: {
///     isDisabled = 0;             // 是否因为安全模式禁用
///     isLoadSuccess = 1;          // 是否加载成功
///     loadResultCode = 0;         // 加载结果返回值
///     localRelativePath = "";     // 沙盒中的相对路径
///     md5 = "";                   // 文件哈希
///     needLoad = 1;               // 是否需要加载
///     versionID = @"1033";        // 配置任务ID【和Bugly上的标签一致，本地补丁为 0】
///     isImmediatelyEffective = NO // 下载后立即加载的补丁，而非重启后才加载的补丁
/// }
RFIX_EXPORT NSNotificationName const RFIXResourceLoadResultNotification;

// 获取 RFIX 版本号
RFIX_EXPORT const char *RFIXVersionString(void);

typedef NS_ENUM(NSUInteger, RFIXEnviroment) {
    // Test 测试环境
    RFIXEnvironmentTest = 1,
    // Production 正式生产环境
    RFIXEnvironmentProduction = 2,
    // 海外版正式生产环境
    RFIXEnvironmentInternationalProduction = 3
};

// RFIX Native 符号类型
// NOTE for RFIX developers: keep this in sync with OCSSymbolType
typedef enum RFIXSymbolType {
    // C 函数类型
    RFIXSymbolTypeFunction = 1,
    // 全局变量类型
    RFIXSymbolTypeVar = 2,
} RFIXSymbolType;

// RFIX Native 符号项目 (全局变量或者 C 函数)
// NOTE: 请不要直接构造该结构体，请使用 RFIX_SYM_FUNC 或 RFIX_SYM_VAR 宏来构造该结构体
// NOTE for RFIX developers: keep this in sync with OCSSymbolItem
typedef struct RFIXSymbolItem {
    // 符号（C 函数或全局变量）类型
    RFIXSymbolType type;
    
    // 符号名称
    // name 必须是一个全局常驻内存的 CFString 变量，即 name 字段在整个 app 声明周期都不被释放
    // 通常 name 由 CFSTR("xxxxxx") 形式构造。
    CFStringRef name;
    
    // 符号地址
    const void *address;
    BOOL isHASH;
} RFIXSymbolItem;

// 构造全局 **非 static**  RFIXSymbolTypeFunction 或 RFIXSymbolTyeVar 类型的 RFIXSymbolItem 结构体
// eg:
// RFIX_SYM_FUNC(CGRectMake)
// RFIX_SYM_VAR(CGRectZero)

#define RFIX_SYM_FUNC(func) {RFIXSymbolTypeFunction, CFSTR(#func), (const void *)(func), NO}
#define RFIX_SYM_VAR(var) {RFIXSymbolTypeVar, CFSTR(#var), (const void *)(&var), NO}

// NOTE: STATIC 变量注册为高级用法，可略过.
// 构造 Static 函数/变量
// 对于 static 变量，名称为 "文件名_变量名", 且文件名中的 '.' 要替换为 '_'
// 对于 static 函数，名称为 "函数名本身"，不需要加文件名前缀.
// eg：
// 例如 FooBar.m 文件中的 static int gBaz;
// 其 name 为 "FooBar_m_gBaz"
/**
 * eg: RFIXTests.m 文件中的 static 函数和变量
 * static int gTestInt = 0;
 * static void someStaticFunction() {}
 * // 可以通过这种方式在 main 函数执行之前将函数或变量注册到 RFIX 运行环境，通常用于注册文件内的 static 函数和变量
 * static RFIXSymbolItem gStaticPrivateSymbols[] = {
 *       // static function 不需要加文件名
 *       RFIX_SYM_STATIC_FUNC("someStaticFunction", someStaticFunction),
 *       // static 变量的 name 需要加上文件名
 *       RFIX_SYM_STATIC_VAR("RFIXTests_m_gTestInt", &gTestInt),
* };
* __attribute__((constructor)) static void registerRFIXSymbolsBeforeMain() {
*    RFIXRegisterSymbols(gStaticPrivateSymbols, RFIX_C_ARRAY_SIZE(gStaticPrivateSymbols));
*}
 */
#define RFIX_SYM_STATIC_FUNC(name, func_addr) {RFIXSymbolTypeFunction, CFSTR(name), (const void *)(func_addr)}
#define RFIX_SYM_STATIC_VAR(name, var_addr) {RFIXSymbolTypeVar, CFSTR(name), (const void *)(var_addr)}

#pragma mark - Log API

/// RFIX 日志等级
/// NOTE for RFIX developers: keep this in sync with OCSLoggerLevel
typedef NS_ENUM(NSUInteger, RFIXLoggerLevel) {
    // Release 版本建议打印 Event/Warn/Info 三个级别的 log
    RFIXLogLevelEvent = 1,
    RFIXLogLevelWarn = 2,
    RFIXLogLevelInfo = 3,
    // Debug 版本建议打印 Event/Warn/Info/Debug 级别 log
    RFIXLogLevelDebug = 4,
    // Max 等级暂时可忽略
    RFIXLogLevelMax = 5
};

/// RFIX 日志回调函数定义
/// NOTE for RFIX developers: keep this in sync with OCSLoggerLevel
typedef void (^RFIXLogCallback)(RFIXLoggerLevel logLevel, NSString *logMessage);

/// RFIX 设置日志回调函数
/// |logCallback| log 回调函数指针，可以为空
/// 仅在 RFIXSDKStart 之前调用一次该接口.
/// 多次调用或者在 RFIXSDKStart 之后调用该接口是一个未定义行为!
/// @param logCallback log 回调函数指针，可以为空
RFIX_EXPORT void RFIXSetLogCallback(RFIXLogCallback _Nullable logCallback);


#pragma mark - Core API

/// 单独添加变量或函数到 RFIX 运行环境的接口
/// 可在 RFIXSDKStart 之前或之后调用，并且可被调用多次
/// 建议尽量使用 RFIXSDKStar 之前一次完成所有 C 函数或全局变量的注册.
/// @param symbolItems 添加到 RFIX 运行环境的 C 函数或 全局变量数组. 如果为空指针，则该 API 调用为空操作
/// @param symbolItemCount symbolItems 数组中 RFIXSymbolItem 元素的数量
RFIX_EXPORT void RFIXRegisterSymbols(const RFIXSymbolItem * _Nullable symbolItems, size_t symbolItemCount);

@class RDeliveryDepends;
/// NOTE: 在调用 RFIXSDKStart 之前，请先调用 RFIXSetLogCallback，并调用 RFIXRegisterSymbols 注册 C 函数和变量
/// 启动 RFIXSDK，加载之前请求并保存到**本地的**补丁，并自动发起请求新的补丁配置。
/// 建议在 App 启动时尽早调用.
/// RFIXSDKStart 在整个 App 生命周期仅需要调用一次.
///
/// @param appID 在 RFIX 平台上注册的 App ID（必填）
/// @param appKey 在 RFIX 平台上 App ID 对应的 App Key
/// @param environment RFIX 环境
/// @param uid 用户唯一 id , utf8 编码 c string, `\0` 结尾
/// @param deviceID 设备 ID，例如 QIMEI 或者其他能够标识唯一设备的id
/// @param appVersion app 版本号
/// @param systemVersion 操作系统 版本号
/// @param rdeliveryDepends RDelivery SDK 的依赖. 使用方需要通过该对象将 RDelivery 的依赖传递给 RFIX
RFIX_EXPORT void RFIXSDKStart(const char *appID,
                              const char *appKey,
                              RFIXEnviroment environment,
                              const char * uid,
                              const char * deviceID,
                              const char * appVersion,
                              const char * systemVersion,
                              RDeliveryDepends *rdeliveryDepends);

/// 和 RFIXSDKStart 相同，除了不会自动发起网络补丁配置请求。后续可通过 RDelivery 聚合拉取等方式手动进行补丁配置请求。
/// 该 API 与 RFIXSDKStart 只能二选一。
RFIX_EXPORT void RFIXSDKStartWithoutRequest(const char *appID,
                                            const char *appKey,
                                            RFIXEnviroment environment,
                                            const char * uid,
                                            const char * deviceID,
                                            const char * appVersion,
                                            const char * systemVersion,
                                            RDeliveryDepends *rdeliveryDepends);

@class RDeliverySDK;
/// 获取内部 RDeliverySDK 实例，一般用于聚合请求
/// NOTE: 必须在 RFIXSDKStart 或 RFIXSDKStartWithoutRequest 之后调用
RFIX_EXPORT RDeliverySDK *RFIXGetRDliverySDK(void);

/// 兼容性判断：是否有 RFIXSDKUpdateUid 函数
#define RFIX_HAS_RFIXSDK_UPDATE_UID_API 1

/// 作用：从后台请求补丁配置，下载补丁（补丁重启后生效）
/// 场景：RFIXSDKStart 初始化时会自动向服务器请求补丁配置，但是如果App没有登录（获取不到 uid），会导致体验账号的补丁拉取不到。
/// 注意：
/// 1. 此 API 应该在 RFIXSDKStart 之后调用（通常在登录成功时）
/// 2. 如果 uid （对比 RFIXSDKStart 时）没有变化，调用会被拦截
/// 3. 重复调用不会生效
///
/// @param uid 用户唯一标识. 形式有由业务自己决定 （例如 qq uin 等）. 主要用于号码包配置. 可以为空.
RFIX_EXPORT void RFIXSDKUpdateUid(const char* _Nullable uid);

/// 仅更新 uid，不会发起网络补丁配置请求。其他参考 RFIXSDKUpdateUid
RFIX_EXPORT void RFIXSDKUpdateUidWithoutRequest(const char* _Nullable uid);

/// PluginID 由业务方按自定义规则产生，要求是能唯一标识当前 App 版本所有插件的集合。
///
/// RFIX SDK 无需理解 PluginID 的含义，以及 PluginID 和当前插件列表的对应关系。（业务方需要能根据 PluginID 反查包含了哪些插件）
/// 如果其中一个插件更新/禁用/删除，或者新增一个插件，PluginID 需要更新
/// PluginID 发生变化，需要立即调用 RFIXSDKUpdatePluginID 告知 RFIX SDK
/// 当 App 没有任何插件，业务需要传 PluginID = nil
RFIX_EXPORT void RFIXSDKUpdatePluginID(const char * _Nullable pluginID);

/// 发起请求补丁(限频60s)
RFIX_EXPORT void RFIXSDKRequestRefresh(void);

/// RFIX SDK 安全模式：防止补丁连续 crash
/// 在 Bugly 发生 Crash 时的 attachmentForException: 代理中，调用 RFIXNotifyCrash(exception)，通知 RFIX SDK 发生了 crash
/// 如果在短时间内（10s）连续启动（2min内）发生 Crash，RFIX 会删除有问题的补丁！
/// @param exception NSException 可以为 nil。内部逻辑仅记录 exception 的 description，不会对其进行处理。
RFIX_EXPORT void RFIXNotifyCrash(NSException *exception);


/// 更新 Bugly 上的 hotpatch 版本（RFIX需要和Bugly联动，这对两者的启动顺序有要求！）
/// 背景：当 RFIX 下发补丁后，你会在 bugly.oa 上看到一个新版本： 1.0.0(hotpatch: 888) 。
/// 这实际是个潜规则：要求 RFIX 在 Bugly 之前启动，才能在 Bugly 规定的 UserDefaults 中写入对应字段，Bugly 启动后就会读取 hotpatch 版本。
///
/// 如果你希望【 Bugly 在 RFIX 之前启动】，则会打破这种潜规则！解决的办法是，在 Bugly 启动之前，单独调用此接口更新 RFIX 在 Bugly 上的标签
/// 此时执行顺序应该是： 1.RFIXUpdateBuglyLabelWithCurrentAppVersion() 2. Bugly 的启动  3. RFIX 的启动（RFIXSDKStart）
/// |currentAppVersion| 参数：当前 App Version，与 RFIXSDKStart 中的 appVersion 参数保持一致
RFIX_EXPORT void RFIXUpdateBuglyLabelWithCurrentAppVersion(const char *_Nullable currentAppVersion);

/// 已加载补丁信息，返回 info dictionary 同 RFIXResourceLoadResultNotification
/// 在 RFIXSDKStart 执行后的任何时间调用此接口都可以获取到本地的补丁信息（是否禁用/是否加载/加载结果）
RFIX_EXPORT NSDictionary *RFIXQueryLocalResourceInfo(void);

/// RFIX SDK 在发起拉取补丁配置网络请求前的回调函数。可用于拦截请求、限制请求频率等。
/// 返回 YES 表示*继续*发起该次拉取补丁配置的网络请求
/// 返回 NO 表示*禁止*（拦截）发器该次请求
typedef BOOL(^RFIXWillGetConfigCallback)(void);
/// 设置 SDK 在发起拉取补丁配置网络请求前的回调函数
/// @param callback 回调函数，回调函数会在*非主线程*被调用，请注意线程安全
///                  回调函数为 nil 表示清空之前设置的回调函数。
/// @note 由于补丁的回滚、停止、撤回也需要 SDK 通过拉取补丁配置来实现。
///       使用该接口拦截请求可能会影响补丁的回滚、停止、撤回、更新等操作，请根据实际情况酌情使用。
///       建议通过配置开关，远程对该回调的行为进行控制，以免造成急需回滚补丁而不得的情况。
RFIX_EXPORT void RFIXSetWillGetConfigCallback(RFIXWillGetConfigCallback callback);

#pragma mark - Diag API

/// RFIX 诊断，仅用于打印日志来调试和定位问题
/// 不要依赖诊断返回的信息中的任何字段作为逻辑判断的依据！！！字段修改不会另行告知！！！
RFIX_EXPORT void RFIXDiagnostic(void (^completion)(NSDictionary *messages));


#pragma mark - Assets API

/// 获取补丁中的图片资源，支持 @1x @2x @3x（内部使用 initWithContentsOfFile:）
/// @param name 图片资源名字，需要加上图片类型扩展名！（如：001.jpg/002.JPEG/002.png）
RFIX_EXPORT RFIXImage * RFIXPatchAssetsImageNamed(NSString *name);

/// 获取补丁中的资源文件
/// @param resource 资源名字
/// @param type 资源扩展名
RFIX_EXPORT NSString * RFIXPatchAssetsPathForResource(NSString *resource, NSString *type);

#pragma mark -
#pragma mark - Deprecated API

/// 这个 API 已经废弃，不再使用
/// RFIX 补丁执行结果（业务方主动调用）
/// @param eventKey 修复事件名
/// @param success 修复结果（业务方自行判断修复结果）
RFIX_EXPORT void RFIXReportExecutionEvent(NSString *eventKey, BOOL success)
DEPRECATED_MSG_ATTRIBUTE("Do NOT use this API, it is deprecated.");

// 请使用 RFIXUpdateBuglyLabelWithCurrentAppVersion，
// 不要使用 RFIXUpdateBuglyLabel。
// 这个 API 无法准确判断 AppVersion，已经废弃.
RFIX_EXPORT void RFIXUpdateBuglyLabel(void)
DEPRECATED_MSG_ATTRIBUTE("Please use RFIXUpdateBuglyLabelWithCurrentAppVersion");

/// 已废弃，推荐使用 RFIXSDKUpdateUid。（当前行为等价于 RFIXSDKUpdateUid，如果 uid 相对于 SDKStart 时没有变化，不会拉取配置）
/// @param uid 用户唯一标识
RFIX_EXPORT void RFIXSDKRequestUpdate(const char* _Nullable uid) DEPRECATED_MSG_ATTRIBUTE("Please use RFIXSDKUpdateUid");


NS_ASSUME_NONNULL_END
#endif  // OCSENGINE_RFIX_RFIX_H_
